package server;

import java.io.*;
import java.net.*;
import java.security.KeyStore;

import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.net.ServerSocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManagerFactory;
import javax.security.cert.X509Certificate;
import server.DatabaseGuard;
import server.RequestObject;

public class DatabaseServer extends Thread {

	private ServerSocket reqObjSocket;
	private DatabaseGuard dbg;
	private static final int port = 4001;

	public DatabaseServer(DatabaseGuard dbg, ServerSocket ss) throws Exception {
		reqObjSocket = ss;
		this.dbg = dbg;
		System.out.println("Server listening on port 4001.");
		
		this.start();
	}
	
	public static void main(String[] args) {
		Database db = new Database();
		DatabaseGuard dbg = new DatabaseGuard(db);
		
		db.openConnection();
		
		try {
			
			ServerSocketFactory ssf = DatabaseServer.getServerSocketFactory();
			ServerSocket ss = ssf.createServerSocket(port);
			
			((SSLServerSocket) ss).setNeedClientAuth(true);
			
			new DatabaseServer(dbg,ss);
		} catch (Exception e) {
			System.out.println("Couldn't start RequestObjectServer");
			e.printStackTrace();
		}
	}

	private static ServerSocketFactory getServerSocketFactory() {
		SSLServerSocketFactory ssf = null;
		try {
			// set up key manager to do server authentication
			SSLContext ctx;
			KeyManagerFactory kmf;
			KeyStore ks;
			TrustManagerFactory tmf;
			KeyStore ts;

			char[] passphrase = "project2".toCharArray();

			ctx = SSLContext.getInstance("TLS");
			kmf = KeyManagerFactory.getInstance("SunX509");
			ks = KeyStore.getInstance("JKS");

			tmf = TrustManagerFactory.getInstance("SunX509");
			ts = KeyStore.getInstance("JKS");

			ts.load(new FileInputStream(
					"/Users/Martin/certificates/serverTrustStore.ts"),
					passphrase);
			tmf.init(ts);

			ks.load(new FileInputStream("/Users/Martin/certificates/serverKeyStore.jks"),
							passphrase);
			kmf.init(ks, passphrase);
			ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

			ssf = ctx.getServerSocketFactory();
			return ssf;
		} catch (Exception e) {
			System.out.println("Exception i ServerSocketFactory()");
			e.printStackTrace();
		}
		return null;
	}

	public void run() {
		while (true) {
			SSLSocket socket;
			String commonName = null;

			// accept a connection
			try {
				System.out.println("Waiting for connections.");
				
				socket = (SSLSocket) reqObjSocket.accept();
				SSLSession session = socket.getSession();
				X509Certificate cert = (X509Certificate) session.getPeerCertificateChain()[0];
				String subject = cert.getSubjectDN().getName();
				
				
				//save the cn and store it with au to authenticate each request from the connect class
				LdapName ldapDN = new LdapName(subject);
				for(Rdn rdn: ldapDN.getRdns()) {
				    System.out.println(rdn.getType() + " -> " + rdn.getValue());
				    if (rdn.getType().equals("CN")) commonName = (String) rdn.getValue();
				}

				
				System.out.println (subject);
				
				
//				Socket client = reqObjSocket.accept();
				System.out.println("Accepted a connection from: "
						+ socket.getInetAddress());
				
				
				
				new Connect(socket, dbg, commonName);
			} catch (Exception e) {
				System.out.println("Exception i ReqObjServer.run()");
			}
		}
	}
}

class Connect extends Thread {
//	private Socket client = null;
	private SSLSocket client = null;
	private DatabaseGuard dbg;
	private ObjectInputStream ois = null;
	private ObjectOutputStream oos = null;
	private String commonName;
	
	public Connect(SSLSocket clientSocket, DatabaseGuard dbg, String commonName) {
		client = clientSocket;
		this.commonName = commonName;
		System.out.println(client);
		this.dbg = dbg;
		try {
			ois = new ObjectInputStream(client.getInputStream());
			oos = new ObjectOutputStream(client.getOutputStream());
		} catch (Exception e1) {
			try {
				client.close();
			} catch (Exception e) {
				System.out.println("Exception i Connect-konstruktor"
						+ e.getMessage());
			}
			return;
		}
		this.start();
	}

	public void run() {
		RequestObject request = null;
		RequestObject response = null;
		int lastcommand = 1;
		try {
			
			oos = new ObjectOutputStream(client.getOutputStream());
			
			response = new RequestObject(1337, dbg.getSalt(commonName), "");
			oos.writeObject(response);
			
			oos.flush();
			
			while (lastcommand != 9) {
				
				ois = new ObjectInputStream(client.getInputStream());
				oos = new ObjectOutputStream(client.getOutputStream());
				
				request = (RequestObject) ois.readObject();

				System.out.println("Request: " + request);
				
				if (request.getCommand() == 0
						&& dbg.logInUser(request.text1(), request.text2(), commonName)) {
					response = new RequestObject(0, "Du ar inloggad", "");
				} else if (dbg.loggedIn()) {
					response = dbg.menu(request);
				} else {
					response = new RequestObject(100, "Du ar inte inloggad", "");
				}
				
				lastcommand = response.getCommand();
				
				System.out.println("Response: " + response);

				oos.writeObject(response);
				oos.flush();
			}
			// close connections
			ois.close();
			oos.close();
			client.close();
		} catch (Exception e) {
			System.out.println("Exception i Connect.run() " + e.getMessage());
		}
	}
}
